导航菜单

Data Binding

Article01/31/2019

July 2016

Volume 31 Number 7

[Data Binding]A Better Way to Implement Data Binding in .NET

By Mark Sowul

Data binding is a powerful technique for developing UIs: It makes it easier to separate view logic from business logic, and easier to test the resulting code. Although present in the Microsoft .NET Framework since the beginning, data binding became more prominent with the advent of Windows Presentation Foundation (WPF) and XAML, as it forms the “glue” between the View and ViewModel in the Model-View-ViewModel (MVVM) pattern.

The drawback of implementing data binding has always been the need for magic strings and boilerplate code, both to broadcast changes to the properties and to bind UI elements to them. Over the years, various toolkits and techniques have come along to lessen the pain; this article aims to simplify the process even further.

First, I’ll review the basics of implementing data binding, as well as common techniques to simplify it (if you’re already familiar with the subject, feel free to skip those sections). After that, I’ll develop a technique you might not have considered (“A Third Way”), and introduce solutions to related design difficulties encountered when developing applications using MVVM. You can obtain the finished version of the framework I develop here in the accompanying code download, or add the SolSoft.DataBinding NuGet package to your own projects.

The Basics: INotifyPropertyChanged

Implementing INotifyPropertyChanged is the preferred way to enable an object to be bound to a UI. It’s simple enough, containing just one member: the PropertyChanged event. The object should raise this event when a bindable property changes, in order to notify the view that it should refresh its representation of the property’s value.

The interface is simple, but implementing it isn’t. Manually raising the event with hardcoded textual property names isn’t a solution that scales well, nor does it stand up to refactoring: You must take care to ensure the textual name remains in sync with the property name in the code. This will not endear you to your successors. Here’s an example:

public int UnreadItemCount{  get  {    return m_unreadItemCount;  }  set  {    m_unreadItemCount = value;    OnNotifyPropertyChanged(      new PropertyChangedEventArgs("UnreadItemCount")); // Yuck  }}

There are several techniques people have developed in response, in order to maintain their sanity (see, for example, the Stack Overflow question at bit.ly/24ZQ7CY); most of them fall into one of two types.

Common Technique 1: Base Class

One way to simplify the situation is with a base class, in order to reuse some of the boilerplate logic. This also provides a few ways to obtain the property name programmatically, instead of having to hard code it.

Getting the Property Name with Expressions: The .NET Framework 3.5 introduced expressions, which allow for runtime inspection of code structure. LINQ uses this API to great effect, for example, to translate .NET LINQ queries into SQL statements. Enterprising developers have also leveraged this API to inspect property names. Using a base class to do this inspection, the preceding setter could be rewritten as:

public int UnreadItemCount...set{  m_unreadItemCount = value;  RaiseNotifyPropertyChanged(() => UnreadItemCount);}

In this way, renaming UnreadItemCount will also rename the expression reference, so the code will still work. The signature of RaiseNotifyPropertyChanged would be as follows:

void RaiseNotifyPropertyChanged(Expression memberExpression)

Various techniques exist for retrieving the property name from the memberExpression. The C# MSDN blog at bit.ly/25baMHM provides a simple example:

public static string GetName(Expression e){  var member = (MemberExpression)e.Body;  return member.Member.Name;}

StackOverflow presents a more thorough listing at bit.ly/23Xczu2. In any case, there’s a downside to this technique: Retrieving the name of the expression uses reflection, and reflection is slow. The performance overhead can be significant, depending on how many property change notifications there are.

Getting the Property Name with CallerMemberName: C# 5.0 and the .NET Framework 4.5 brought an additional way to retrieve the property name, using the CallerMemberName attribute (you can use this with older versions of the .NET Framework via the Microsoft.Bcl NuGet package). This time, the compiler does all the work, so there’s no runtime overhead. With this approach, the method becomes:

void RaiseNotifyPropertyChanged([CallerMemberName] string propertyName = "")And the call to it is:public int UnreadItemCount...set{  m_unreadItemCount = value;  RaiseNotifyPropertyChanged();}

The attribute instructs the compiler to fill in the caller name, UnreadItemCount, as the value of the optional parameter propertyName.

Getting the Property Name with nameof: The CallerMemberName attribute was probably tailor-made for this use case (raising PropertyChanged in a base class), but in C# 6, the compiler team finally provided something much more broadly useful: the nameof keyword. Nameof is handy for many purposes; in this case, if I replace the expressions-based code with nameof, once again the compiler will do all the work (no runtime overhead). It’s worth noting this is strictly a compiler version feature and not a .NET version feature: You could use this technique and still target the .NET Framework 2.0. However, you (and all your team members) have to be using at least Visual Studio 2015. Using nameof looks like this:

public int UnreadItemCount...set{  m_unreadItemCount = value;  RaiseNotifyPropertyChanged(nameof(UnreadItemCount));}

There’s a general problem, though, with any base class technique: It “burns your base class,” as the saying goes. If you want your view model to extend a different class, you’re out of luck. It also does nothing to handle “dependent” properties (for example, a FullName property that concatenates FirstName and LastName: Any change to FirstName or LastName must also trigger a change on FullName).

Common Technique 2: Aspect-Oriented Programming

Aspect-oriented programming (AOP) is a technique that basically “post-processes” your compiled code, either at run time or with a post-compilation step, in order to add certain behaviors (known as an “aspect”). Usually, the aim is to replace repetitive boilerplate code, such as logging or exception handling (so-called “cross-cutting concerns”). Not surprisingly, implementing INotifyPropertyChanged is a good candidate.

There are several toolkits available for this approach. PostSharp is one (bit.ly/1Xmq4n2). I was pleasantly surprised to learn that it properly handles dependent properties (for example, the FullName property described earlier). An open source framework called “Fody” is similar (bit.ly/1wXR2VA).

This is an attractive approach; its drawbacks might not be significant. Some implementations intercept behavior at run time, which incurs a performance cost. The post-compilation frameworks, in contrast, shouldn’t introduce any runtime overhead, but might require some sort of install or configuration. PostSharp is currently offered as an extension to Visual Studio. Its free “Express” edition limits use of the INotifyPropertyChanged aspect to 10 classes, so this likely means a monetary cost. Fody, on the other hand, is a free NuGet package, which makes it appear to be a compelling choice. Regardless, consider that with any AOP framework the code you write isn’t exactly the same code you’ll be running … and debugging.

A Third Way

An alternative way of handling this is to leverage object-oriented design: Have the properties themselves be responsible for raising the events! It’s not a particularly revolutionary idea, but it’s not one I’ve encountered outside of my own projects. In its most basic form, it might look like the following:

public class NotifyProperty{  public NotifyProperty(INotifyPropertyChanged owner, string name, T initialValue);  public string Name { get; }  public T Value { get; }  public void SetValue(T newValue);}

The idea is that you provide the property with its name and a reference to its owner, and let it do the work of raising the PropertyChanged event—something like:

public void SetValue(T newValue){  if(newValue != m_value)  {    m_value = newValue;    m_owner.PropertyChanged(m_owner, new PropertyChangedEventArgs(Name));  }}

The problem is that this won’t actually work: I can’t raise an event from another class like that. I need some sort of contract with the owning class to allow me to raise its PropertyChanged event: that’s exactly the job of an interface, so I’ll create one:

public interface IRaisePropertyChanged{  void RaisePropertyChanged(string propertyName)}

Once I have this interface, I can actually implement Notify­Property.SetValue:

public void SetValue(T newValue){  if(newValue != m_value)  {    m_value = newValue;    m_owner.RaisePropertyChanged(this.Name);  }}

Implementing IRaisePropertyChanged: Requiring the property owner to implement an interface does mean that each view model class will require some boilerplate, as illustrated in Figure 1. The first part is required for any class to implement INotifyPropertyChanged; the second part is specific to the new IRaisePropertyChanged. Note that because the RaisePropertyChanged method isn’t intended for general use, I prefer to implement it explicitly.

Figure 1 Code Required to Implement IRaisePropertyChanged

// PART 1: required for any class that implements INotifyPropertyChangedpublic event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(PropertyChangedEventArgs args){  // In C# 6, you can use PropertyChanged?.Invoke.  // Otherwise I'd suggest an extension method.  var toRaise = PropertyChanged;  if (toRaise != null)    toRaise(this, args);}// PART 2: IRaisePropertyChanged-specificprotected virtual void RaisePropertyChanged(string propertyName){  OnPropertyChanged(new PropertyChangedEventArgs(propertyName));}// This method is only really for the sake of the interface,// not for general usage, so I implement it explicitly.void IRaisePropertyChanged.RaisePropertyChanged(string propertyName){  this.RaisePropertyChanged(propertyName);}

I could put this boilerplate in a base class and extend it, which seems to bring me back to my earlier discussion. After all, if I apply CallerMemberName to the RaisePropertyChanged method, I’ve basically reinvented the first technique, so what’s the point? In both cases, I could just copy the boilerplate to other classes if they can’t derive from a base class.

One key difference compared to the earlier base class technique is that in this case there’s no real logic in the boilerplate; all the logic is encapsulated in the NotifyProperty class. Checking whether the property value has changed before raising the event is simple logic, but it’s still better not to duplicate it. Consider what would happen if you wanted to use a different IEqualityComparer to do the check. With this model, you’d need to alter only the NotifyProperty class. Even if you had multiple classes with the same IRaisePropertyChanged boilerplate, each implementation could benefit from the changes to NotifyProperty without having to change any code itself. Regardless of any behavior changes you might wish to introduce, the IRaisePropertyChanged code is very unlikely to change.

Putting the Pieces Together: Now I have the interface the view model needs to implement, and the NotifyProperty class used for the properties that will be data bound. The last step is constructing the NotifyProperty; for that, you still need to pass in a property name, somehow. If you’re lucky enough to be using C# 6, this is easily done with the nameof operator. If not, you can instead create the NotifyProperty with the aid of expressions, such as by using an extension method (unfortunately, there’s nowhere for Caller­MemberName to help this time):

public static NotifyProperty CreateNotifyProperty(  this IRaisePropertyChanged owner,  Expression nameExpression, T initialValue){  return new NotifyProperty(owner,    ObjectNamingExtensions.GetName(nameExpression),    initialValue);}// Listing of GetName provided earlier

With this approach, you’ll still pay a reflection cost, but only when creating an object, rather than every time a property changes. If that’s still too expensive (you’re creating many objects), you can always cache a call to GetName, and keep that as a static readonly value in the view model class. For either case, Figure 2 shows an example of a simple view model.

Figure 2 A Basic ViewModel with a NotifyProperty

public class LogInViewModel : IRaisePropertyChanged{  public LogInViewModel()  {    // C# 6    this.m_userNameProperty = new NotifyProperty(      this, nameof(UserName), null);    // Extension method using expressions    this.m_userNameProperty = this.CreateNotifyProperty(() => UserName, null);  }  private readonly NotifyProperty m_userNameProperty;  public string UserName  {    get    {      return m_userNameProperty.Value;    }    set    {      m_userNameProperty.SetValue(value);    }  }  // Plus the IRaisePropertyChanged code in Figure 1 (otherwise, use a base class)}

Binding and Renaming:While I’m talking about names, it’s a good time to discuss another data-binding concern. Safely raising the PropertyChanged event without a hardcoded string is half the battle to survive refactoring; the data binding itself is the other half. If you rename a property that’s used for a binding in XAML, success is, shall I say, not guaranteed (see, for example, bit.ly/1WCWE5m).

The alternative is to code the data bindings manually in the codebehind file. For example:

// Constructorpublic LogInDialog(){  InitializeComponent();  LogInViewModel forNaming = null;  m_textBoxUserName.SetBinding(TextBox.TextProperty,    ObjectNamingExtensions.GetName(() => forNaming.UserName);  // Or with C# 6, just nameof(LogInViewModel.UserName)}

It’s a little weird to have that null object solely for leveraging the expressions functionality, but it does work (you don’t need it if you have access to nameof).

I find this technique valuable, but I do recognize the tradeoffs. On the plus side, if I rename the UserName property, I can be confident that the refactoring will work. Another significant benefit is that “Find All References” works just as expected.

On the minus side, it’s not necessarily as simple and natural as doing the binding in XAML, and it prevents me from keeping the UI design “independent.” I can’t just redesign the appearance in the Blend tool without changing code, for example. Additionally, this technique doesn’t work against data templates; you can extract that template into a custom control, but that’s more effort.

In total, I gain flexibility to change the “data model” side, at the cost of flexibility on the “view” side. Overall, it’s up to you whether the advantages justify declaring the bindings this way.

“Derived” Properties

Earlier, I described a scenario in which it’s particularly inconvenient to raise the PropertyChanged event, namely for those properties whose value depends on other properties. I mentioned the simple example of a FullName property that depends on FirstName and LastName. My goal for implementing this scenario is to take in those base NotifyProperty objects (FirstName and LastName), as well as the function to calculate the derived value from them (for example, FirstName.Value + " " + LastName.Value), and with that, produce a property object that will automatically handle the rest for me. To enable this, there are a couple of tweaks I’ll make to my original NotifyProperty.

The first task is to expose a separate ValueChanged event on NotifyProperty. The derived property will listen to this event on its underlying properties, and respond by calculating a new value (and raising the appropriate PropertyChanged event for itself). The second task is to extract an interface, IProperty, to encap­sulate the general NotifyProperty functionality. Among other things, this allows me to have derived properties come from other derived properties. The resulting interface is straightforward and is listed here (the corresponding changes to NotifyProperty are very simple, so I won’t list them):

public interface IProperty{  string Name { get; }  event EventHandler ValueChanged;  TValue Value { get; }}

Creating the DerivedNotifyProperty class seems straightforward, too, until you start trying to put the pieces together. The basic idea was to take in the underlying properties and a function to calculate some new value from them, but that immediately runs into trouble because of generics. There’s no practical way to take in multiple different property types:

// Attempted constructorpublic DerivedNotifyProperty(IRaisePropertyChanged owner,  string propertyName, IProperty property1, IProperty property2,  Func derivedValueFunction)

I can get around the first half of the issue (accepting multiple generic types) by using static Create methods instead:

static DerivedNotifyProperty CreateDerivedNotifyProperty  (this IRaisePropertyChanged owner,  string propertyName, IProperty property1, IProperty property2,  Func derivedValueFunction)

But the derived property still needs to listen for the ValueChanged event on each base property. Solving this requires two steps. First, I’ll extract the ValueChanged event into a separate interface:

public interface INotifyValueChanged // No generic type!{  event EventHandler ValueChanged;}public interface IProperty : INotifyValueChanged{  string Name { get; }  TValue Value { get; }}

This allows the DerivedNotifyProperty to take in the non-generic INotifyValueChanged, instead of the generic IProperty. Second, I need to calculate the new value without generics: I’ll take the original derivedValueFunction that accepts the two generic parameters and from that create a new anonymous function that doesn’t require any parameters—instead, it will reference the values of the two properties passed in. In other words, I’ll create a closure. You can see this process in the following code:

static DerivedNotifyProperty CreateDerivedNotifyProperty  (this IRaisePropertyChanged owner,  string propertyName, IProperty property1, IProperty property2,  Func derivedValueFunction){  // Closure  Func newDerivedValueFunction =    () => derivedValueFunction (property1.Value, property2.Value);  return new DerivedNotifyProperty(owner, propertyName,    newDerivedValueFunction, property1, property2);}

The new “derived value” function is just Func with no parameters; now the DerivedNotifyProperty requires no knowledge of the underlying property types, so I can happily create one from multiple properties of different types.

The other subtlety is when to actually call that derived value function. An obvious implementation would be to listen for the ValueChanged event of each underlying property and call the function whenever a property changes, but that’s inefficient when multiple underlying properties change in the same operation (imagine a “Reset” button that clears out a form). A better idea is to produce the value on demand (and cache it), and invalidate it if any of the underlying properties change. Lazy is a perfect way to implement this.

You can see an abbreviated listing of the DerivedNotifyProperty class in Figure 3. Notice that the class takes in an arbitrary number of properties to listen to—although I list only the Create method for two underlying properties, I create additional overloads that take in one underlying property, three underlying properties and so on.

Figure 3 Core Implementation of DerivedNotifyProperty

public class DerivedNotifyProperty : IProperty{  private readonly IRaisePropertyChanged m_owner;  private readonly Func m_getValueProperty;  public DerivedNotifyProperty(IRaisePropertyChanged owner,    string derivedPropertyName, Func getDerivedPropertyValue,    params INotifyValueChanged[] valueChangesToListenFor)  {    this.m_owner = owner;    this.Name = derivedPropertyName;    this.m_getValueProperty = getDerivedPropertyValue;    this.m_value = new Lazy(m_getValueProperty);    foreach (INotifyValueChanged valueChangeToListenFor in valueChangesToListenFor)      valueChangeToListenFor.ValueChanged += (sender, e) => RefreshProperty();  }  // Name property and ValueChanged event omitted for brevity   private Lazy m_value;  public TValue Value  {    get    {      return m_value.Value;    }  }  public void RefreshProperty()  {    // Ensure we retrieve the value anew the next time it is requested    this.m_value = new Lazy(m_getValueProperty);    OnValueChanged(new ValueChangedEventArgs());    m_owner.RaisePropertyChanged(Name);  }}

Note that the underlying properties could come from different owners. For example, assume you have an Address view model with an IsAddressValid property. You also have an Order view model that contains two Address view models, for billing and shipping addresses. It would be reasonable to create an IsOrderValid property on the parent Order view model that combines the IsAddressValid properties of the child Address view models, so you can submit the order only if both addresses are valid. To do this, the Address view model would expose both bool IsAddressValid { get; } and IProperty IsAddressValidProperty { get; }, so the Order view model can create a DerivedNotifyProperty that references the child IsAddressValidProperty objects.

The Usefulness of DerivedNotifyProperty

The FullName example I gave for a derived property is fairly contrived, but I do want to discuss some real use cases and tie them to some design principles. I just touched on one example: IsValid. This is a fairly simple and powerful way to disable the “Save” button on a form, for example. Note that there’s nothing that forces you to use this technique only in the context of a UI view model. You can use it to validate business objects, too; they just need to implement IRaisePropertyChanged.

A second situation where derived properties are extremely useful is in “drill down” scenarios. As a simple example, consider a combo box for selecting a country, where selecting a country populates a list of cities. You can have SelectedCountry be a NotifyProperty and, given a GetCitiesForCountry method, create AvailableCities as a DerivedNotifyProperty that will automatically stay in sync when the selected country changes.

A third area where I’ve used NotifyProperty objects is for indicating whether an object is “busy.” While the object is considered busy, certain UI features should be disabled, and perhaps the user should see a progress indicator. This is a seemingly simple scenario, but there’s a lot of subtlety here to poke at.

The first part is tracking whether the object is busy; in the simple case, I can do that with a Boolean NotifyProperty. However, what often happens is that an object can be “busy” for one of multiple reasons: let’s say I’m loading several areas of data, possibly in parallel. The overall “busy” state should depend on whether any of those items are still in progress. That almost sounds like a job for derived properties, but it would be clunky (if not impossible): I’d need a property for each possible operation to track whether it’s in progress. Instead, I want to do something like the following for each operation, using a single IsBusy property:

try{  IsBusy.SetValue(true);  await LongRunningOperation();}finally{  IsBusy.SetValue(false);}

To enable this, I create an IsBusyNotifyProperty class that extends NotifyProperty, and in it, keep a “busy count.” I override SetValue such that SetValue(true) increases that count, and Set­Value(false) decreases it. When the count goes from 0 to 1, only then do I call base.SetValue(true), and when it goes from 1 to 0, base.SetValue(false). In this way, starting multiple outstanding operations results in IsBusy becoming true only once, and thereafter it becomes false again only when they’re all finished. You can see the implementation in the code download.

That takes care of the “busy” side of things: I can bind “is busy” to the visibility of a progress indicator. However, for disabling the UI, I need the opposite. When “is busy” is true, “UI enabled” should be false.

XAML has the concept of an IValueConverter, which converts a value to (or from) a display representation. A ubiquitous example is BooleanToVisibilityConverter—in XAML, an element’s “Visibility” isn’t described by a Boolean, but rather an enum value. This means it’s not possible to bind an element’s visibility directly to a Boolean property (like IsBusy); you need to bind the value and also use a converter. For example:

I mentioned that “enable the UI” is the opposite of “is busy”; it might be tempting to create a value converter to invert a Boolean property, and use that to do the job:

Indeed, before I created a DerivedNotifyProperty class, that was the easiest way. It was quite tedious to create a separate property, wire it up to be the inverse of IsBusy, and raise the appropriate PropertyChanged event. Now, however, it’s trivial, and without that artificial barrier (that is, laziness) I have a better sense of where it makes sense to use IValueConverter.

Ultimately, the view—however it might be implemented (WPF or Windows Forms, for example; or even a console app is a type of view)—should be a visualization (or “projection”) of what’s happening in the underlying application, and have no responsibility for determining the mechanism and business rules for what’s going on. In this case, the fact that IsBusy and IsEnabled happen to be related to each other so intimately is an implementation detail; it’s not inherent that disabling the UI should be related specifically to whether the application is busy.

As it stands, I consider it a gray area, and wouldn’t argue with you if you did want to use a value converter to implement this. However, I can make a much stronger case by adding another piece to the example. Let’s pretend that if it loses network access, the application should also disable the UI (and show a panel indicating the situation). Well, that makes three situations: If the application is busy, I should disable the UI (and show a progress panel). If the application loses network access, I should also disable the UI (and show a “lost connection” panel). The third situation is when the application is connected and not busy and, thus, ready to accept input.

Trying to implement this without a separate IsEnabled property is awkward at best; you could use a MultiBinding, but that’s still ungainly, and not supported in all environments. Ultimately, that kind of awkwardness usually means there’s a better way, and now we know there is: this logic is better handled inside the view model. It’s now trivial to expose two NotifyProperties, IsBusy and IsDisconnected, and then create a DerivedNotifyProperty, IsEnabled, that’s true only if both of those are false.

If you went the IValueConverter route and bound the UI’s Enabled state directly to IsBusy (with a converter to invert it), you’d have quite a bit of work to do now. If you instead exposed a separate, derived IsEnabled property, adding this new bit of logic is much less work, and the IsEnabled binding itself wouldn’t even need to change. That’s a good sign you’re doing things right.

Wrapping Up

Laying out this framework was a bit of a journey, but the reward is that now I can implement property change notifications without repetitive boilerplate, without magic strings and with support for refactoring. My view models don’t require logic from a particular base class. I can create derived properties that also raise the appropriate change notifications without much additional effort. Finally, the code that I see is the code that’s running. And I get all of that by developing a fairly simple framework with object-oriented design. I hope you find it useful in your own projects.

Mark Sowul has been a devoted .NET developer since the beginning, and shares his wealth of architecture and performance expertise in the Microsoft .NET Framework and SQL Server via his New York consulting business, SolSoft Solutions. Reach him at mark@solsoftsolutions.com. If you find his ideas intriguing and would like to subscribe to his newsletter, sign up at eepurl.com/_K7YD.

Thanks to the following technical experts for reviewing this article: Francis Cheung (Microsoft) and Charles Malm (Zebra Technologies)Francis Cheung is a lead developer for the Microsoft Patterns & Practices Group. Francis has been involved in a diverse array of projects including Prism. He is currently focused on Azure related guidance.

Charles Malm is a game, .NET, and Web software engineer, and co-founder of RealmSource, LLC.

Discuss this article in the MSDN Magazine forum

相关推荐: